Add variable composition and Handlebars template rendering#1731
Closed
dmontagu wants to merge 49 commits into
Closed
Add variable composition and Handlebars template rendering#1731dmontagu wants to merge 49 commits into
dmontagu wants to merge 49 commits into
Conversation
Deploying logfire-docs with
|
| Latest commit: |
c7ae5b9
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://1443a1dc.logfire-docs.pages.dev |
| Branch Preview URL: | https://feature-variable-composition.logfire-docs.pages.dev |
…ame>> reference expansion, and template validation
d414e21 to
9d5a27e
Compare
…osition # Conflicts: # logfire/variables/variable.py # pyproject.toml
Member
Contributor
|
@petyosi I have started the AI code review. It will take a few minutes to complete. |
Member
Contributor
|
@petyosi I have started the AI code review. It will take a few minutes to complete. |
test_unserializable_default_with_references_falls_back needs composition to run (and therefore pydantic_handlebars), but its enclosing class isn't @requires_handlebars. On Python 3.9 the [variables] extra doesn't install pydantic_handlebars and the test fails. Decorate the one test that needs it; the sibling test_unserializable_default_skips_default_composition stays unconditional because it exercises the no-composition short-circuit.
Contributor
|
You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment |
ComposedReference, VariableCompositionError, VariableCompositionCycleError, and ResolutionReason were imported by logfire/variables/__init__.py but missing from __all__, so they weren't picked up by 'from logfire.variables import *' or by auto-generated API docs. They are part of the public surface — ComposedReference is the element type of ResolvedVariable.composed_from and ResolutionReason is the type of ResolvedVariable.reason. Add them to __all__ and import ResolutionReason explicitly.
Merged
alexmojaki
reviewed
May 21, 2026
alexmojaki
reviewed
May 21, 2026
…ic/logfire into feature/variable-composition
…r' into feature/variable-composition
- templates-and-composition.md: merge the template_var definition and usage into one runnable snippet that covers the conditional case, drop the one-row parameters table in favor of prose, and remove the unnecessary skip="true" on the composition example. - index.md: add the missing logfire.configure() call so the snippet does not raise LogfireNotConfiguredWarning and can run unskipped.
_BaseVariable no longer carries any template-related surface area: the public get_template_inputs_schema method is removed and to_config no longer emits a template_inputs_schema field. TemplateVariable now overrides to_config to attach its schema, and external diff/sync code uses a small module-level helper (get_template_inputs_schema) gated on isinstance(variable, TemplateVariable). Also tightens _resolve_code_default (formerly _resolve_serialized_default) so the user's default-resolution function is invoked at most once per get(), and hardens the outer error handler against the default also raising while building the error result.
Extracts _BaseVariable._lookup_serialized to encode the
override -> provider -> registered code default priority once, and
routes both _resolve (for self.name) and the composition expander's
resolve_ref (for child @{ref}@ lookups) through it. The two paths can
no longer drift.
Behavioural notes:
- context_override now returns the serialized form from _lookup_serialized;
_resolve detects the reason and skips composition (preserving the
literal-override semantics), then optionally renders for TemplateVariable.
- When _lookup_serialized falls back to a registered code default, the
caller in _resolve promotes the success reason to 'code_default' and
preserves the provider's exception (matching the previous behaviour
carved out in _resolve_code_default).
This was referenced May 21, 2026
Contributor
Author
Composition expands `@{...}@` references through `pydantic-handlebars`
via `logfire/variables/reference_syntax.py:render_once`, so any doc
example using `@{...}@` requires the `[variables]` extra (Python 3.10+).
Extend the test_docs skip predicate to cover that, matching the
existing `logfire.template_var` skip.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
@{variable_name}@syntax. Composition happens during resolution, before deserialization, and supports nested references, dotted access like@{brand.tagline}@, escaping with\@{...}@, cycle/depth protection, andcomposed_frommetadata on resolved variables and spans.{{placeholder}}templates rendered with runtime inputs. Rendering usespydantic-handlebarsand preserves{{...}}placeholders while@{...}@composition is expanded first.TemplateVariable[T, InputsT]API:logfire.template_var()adds a single-step flow whereget(inputs)resolves the variable, expands@{...}@references, renders{{...}}placeholders, and deserializes to the declared type.logfire.var(..., template_inputs=Inputs)records the template input schema and lets callers resolve first, then callresolved.render(inputs)when they want rendering.Syntax
Composition references use
@{...}@to avoid conflicting with prompt/runtime templating languages:Runtime template inputs continue to use normal Handlebars syntax:
Resolution order:
Main modules
logfire/variables/composition.pylogfire/variables/reference_syntax.py@{...}@rendering shim that preserves runtime{{...}}placeholderslogfire/variables/template_validation.pylogfire/variables/variable.pyVariableandTemplateVariableTest plan
uv run pytest tests/test_variable_composition.py tests/test_variable_templates.py tests/test_template_validation.py tests/test_variables.py -quv run pre-commit run --files docs/reference/advanced/managed-variables/index.md docs/reference/advanced/managed-variables/templates-and-composition.md examples/python/variable_composition_demo.py logfire/_internal/main.py logfire/variables/abstract.py logfire/variables/composition.py logfire/variables/reference_syntax.py logfire/variables/template_validation.py logfire/variables/variable.py tests/test_template_validation.py tests/test_variable_composition.py tests/test_variable_templates.pyuv run mkdocs build --no-strict